﻿using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyModel;
using Microsoft.Extensions.Logging;
using Puppy.Modularity.PlugIns;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Puppy.Modularity
{
    public class ModuleLoader : IModuleLoader
    {
        public IPuppyModuleDescriptor[] LoadModules(
            IServiceCollection services,
            PlugInSourceList plugInSources)
        {
            Check.NotNull(services, nameof(services));
            Check.NotNull(plugInSources, nameof(plugInSources));

            var modules = GetDescriptors(services, plugInSources);

            return modules.ToArray();
        }

        private List<IPuppyModuleDescriptor> GetDescriptors(
            IServiceCollection services,
            PlugInSourceList plugInSources)
        {
            var modules = new List<IPuppyModuleDescriptor>();

            FillModules(modules, services, plugInSources);

            return modules.Cast<IPuppyModuleDescriptor>().ToList();
        }

        protected virtual void FillModules(
            List<IPuppyModuleDescriptor> modules,
            IServiceCollection services,
            PlugInSourceList plugInSources)
        {
            foreach(var moduleType in FindAllModuleTypes())
            {
                modules.Add(CreateModuleDescriptor(services, moduleType));
            }

            foreach (var moduleType in plugInSources.GetAllModules())
            {
                if (modules.Any(m => m.Type == moduleType))
                {
                    continue;
                }

                modules.Add(CreateModuleDescriptor(services, moduleType));
            }
        }

        protected virtual IPuppyModuleDescriptor CreateModuleDescriptor(IServiceCollection services, Type moduleType)
        {
            return new PuppyModuleDescriptor(moduleType, CreateAndRegisterModule(services, moduleType));
        }

        protected virtual IPuppyModule CreateAndRegisterModule(IServiceCollection services, Type moduleType)
        {
            var module = (IPuppyModule)Activator.CreateInstance(moduleType);
            services.AddSingleton(moduleType, module);
            return module;
        }

        private List<Type> FindAllModuleTypes()
        {
            var moduleTypes = new List<Type>();
            Console.WriteLine("Loaded ABP modules:");

            var startAssembly = Assembly.GetEntryAssembly();
            var types = startAssembly.GetTypes();
            var startType = types.FirstOrDefault(p => PuppyModule.IsPuppyModule(p));

            AddModuleAndDependenciesRecursively(moduleTypes, startType);

            return moduleTypes;
        }

        private void AddModuleAndDependenciesRecursively(
            List<Type> moduleTypes,
            Type moduleType,
            int depth = 0)
        {
            if (moduleTypes.Contains(moduleType))
            {
                return;
            }

            moduleTypes.Add(moduleType);
            Console.WriteLine($"{new string(' ', depth * 2)}- {moduleType.FullName}");

            foreach (var dependedModuleType in FindDependedModuleTypes(moduleType))
            {
                AddModuleAndDependenciesRecursively(moduleTypes, dependedModuleType, depth + 1);
            }
        }

        public List<Type> FindDependedModuleTypes(Type moduleType)
        {
            var dependencies = new List<Type>();

            var assemblys = moduleType.Assembly.GetReferencedAssemblies().Select(Assembly.Load);

            foreach(var assembly in assemblys)
            {
                foreach(var type in assembly.GetTypes())
                {
                    if (PuppyModule.IsPuppyModule(type))
                    {
                        dependencies.AddIfNotContains(type);
                    }
                }
            }

            return dependencies;
        }
    }
}